import face_recognition
import cv2
import numpy as np
import RPi.GPIO as GPIO
import time
from threading import Thread, Event


class FaceLock:
    def __init__(self) -> None:
        ########### init GPIO #####################
        self.AvoidSensorLeft = 32
        self.AvoidSensorRight = 11
        self.Buzzer = 24

        GPIO.setmode(GPIO.BOARD)
        GPIO.setwarnings(False)
        GPIO.setup(self.AvoidSensorLeft, GPIO.IN)
        GPIO.setup(self.AvoidSensorRight, GPIO.IN)
        GPIO.setup(self.Buzzer, GPIO.OUT, initial=1)

        ########## buzzer thread #################
        self.event = Event()
        self.event.clear()
        self.BuzzerThread = Thread(target=self.buzze)

        ########## init Open CV ##################
        self.video_capture = cv2.VideoCapture(0)
        # Load a sample picture and learn how to recognize it.
        sen_image = face_recognition.load_image_file("imgs/sen.jpg")
        sen_face_encoding = face_recognition.face_encodings(sen_image)[0]

        # Create arrays of known face encodings and their names
        self.known_face_encodings = [
            sen_face_encoding
        ]

        self.known_face_names = [
            "Qian Sen"
        ]

        ############ lock state ###################
        self.closed = True

    def detect_unknown_face(self, frame):
        # Initialize some variables
        face_locations = []
        face_encodings = []
        face_names = []
        is_unknown = True

        # Resize frame of video to 1/4 size for faster face recognition processing
        small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)

        # Convert the image from BGR color (which OpenCV uses) to RGB color (which face_recognition uses)
        rgb_small_frame = small_frame[:, :, ::-1]

        # Find all the faces and face encodings in the current frame of video
        face_locations = face_recognition.face_locations(rgb_small_frame)
        face_encodings = face_recognition.face_encodings(
            rgb_small_frame, face_locations)

        for face_encoding in face_encodings:
            # See if the face is a match for the known face(s)
            matches = face_recognition.compare_faces(
                self.known_face_encodings, face_encoding)
            name = "Unknown"

            # # If a match was found in known_face_encodings, just use the first one.
            # if True in matches:
            #     first_match_index = matches.index(True)
            #     name = known_face_names[first_match_index]

            # Or instead, use the known face with the smallest distance to the new face
            face_distances = face_recognition.face_distance(
                self.known_face_encodings, face_encoding)
            best_match_index = np.argmin(face_distances)
            if matches[best_match_index]:
                name = self.known_face_names[best_match_index]
                is_unknown = False

            face_names.append(name)

        # add some box and text
        for (top, right, bottom, left), name in zip(face_locations, face_names):
            # Scale back up face locations since the frame we detected in was scaled to 1/4 size
            top *= 4
            right *= 4
            bottom *= 4
            left *= 4

            # Draw a box around the face
            cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)

            # Draw a label with a name below the face
            cv2.rectangle(frame, (left, bottom - 35),
                          (right, bottom), (0, 0, 255), cv2.FILLED)
            font = cv2.FONT_HERSHEY_DUPLEX
            cv2.putText(frame, name, (left + 6, bottom - 6),
                        font, 1.0, (255, 255, 255), 1)

        # Display the results
        cv2.imshow('Video', frame)
        return is_unknown

    def wait_for_open(self):
        _, frame = self.video_capture.read()
        # False: OK
        # True: Unkown
        while self.detect_unknown_face(frame):
            time.sleep(0.3)
            _, frame = self.video_capture.read()

    def buzze(self):
        while not self.event.is_set():
            GPIO.output(self.Buzzer, 0)
            time.sleep(0.5)
            GPIO.output(self.Buzzer, 1)

        GPIO.cleanup()

    def open_the_door(self):
        # self.BuzzerThread.start()
        print("door opened")
        self.closed = False

    def wait_for_close(self):
        closed = False
        while not closed:
            time.sleep(0.3)
            LeftSensorValue = GPIO.input(self.AvoidSensorLeft)
            RightSensorValue = GPIO.input(self.AvoidSensorRight)
            # if is closed, the indicator light of the infrared obstacle avoidance module is on, and the port level is LOW
            closed = LeftSensorValue == False or RightSensorValue == False

    def close_the_door(self):
        self.event.set()
        print("door closed")
        self.closed = True

    def stop(self):
        GPIO.cleanup()
        # Release handle to the webcam
        self.video_capture.release()
        cv2.destroyAllWindows()

    def run(self):
        while True:
            if self.closed:
                self.wait_for_open()
                self.open_the_door()
            else:
                self.wait_for_close()
                self.close_the_door()


# start look
lock = FaceLock()
lock.run()


'''
event = Event()


def wait_for_close():
   # Definition of infrared obstacle avoidance module pin
    AvoidSensorLeft = 32
    AvoidSensorRight = 11
    # Light seeking (Left and right infrared obstacle avoidance) interface
    right_light = 26
    left_light = 31

    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(AvoidSensorLeft, GPIO.IN)
    GPIO.setup(AvoidSensorRight, GPIO.IN)
    LeftSensorValue = GPIO.input(AvoidSensorLeft)
    RightSensorValue = GPIO.input(AvoidSensorRight)

    while True:
        if LeftSensorValue == False and RightSensorValue == False:
            event.set()
            break
        time.sleep(0.3)

    GPIO.clean()


def open_the_door():
    buzzer = 24

    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(buzzer, GPIO.OUT)
    GPIO.output(buzzer, 0)
    while True:
        time.sleep(0.3)
        if event.is_set():
            event.clear()
            break
    GPIO.cleanup()


# This is a demo of running face recognition on live video from your webcam. It's a little more complicated than the
# other example, but it includes some basic performance tweaks to make things run a lot faster:
#   1. Process each video frame at 1/4 resolution (though still display it at full resolution)
#   2. Only detect faces in every other frame of video.
# PLEASE NOTE: This example requires OpenCV (the `cv2` library) to be installed only to read from your webcam.
# OpenCV is *not* required to use the face_recognition library. It's only required if you want to run this
# specific demo. If you have trouble installing it, try any of the other demos that don't require it instead.
# Get a reference to webcam #0 (the default one)
video_capture = cv2.VideoCapture(0)

# Load a sample picture and learn how to recognize it.
sen_image = face_recognition.load_image_file("imgs/sen.jpg")
sen_face_encoding = face_recognition.face_encodings(sen_image)[0]

# Create arrays of known face encodings and their names
known_face_encodings = [
    sen_face_encoding
]

known_face_names = [
    "Qian Sen"
]


def detect_face(frame):

    # Initialize some variables
    face_locations = []
    face_encodings = []
    face_names = []
    process_this_frame = True
    can_open = False

    # Resize frame of video to 1/4 size for faster face recognition processing
    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)

    # Convert the image from BGR color (which OpenCV uses) to RGB color (which face_recognition uses)
    rgb_small_frame = small_frame[:, :, ::-1]

    # Only process every other frame of video to save time
    if process_this_frame:
        # Find all the faces and face encodings in the current frame of video
        face_locations = face_recognition.face_locations(rgb_small_frame)
        face_encodings = face_recognition.face_encodings(
            rgb_small_frame, face_locations)

        face_names = []
        for face_encoding in face_encodings:
            # See if the face is a match for the known face(s)
            matches = face_recognition.compare_faces(
                known_face_encodings, face_encoding)
            name = "Unknown"

            # # If a match was found in known_face_encodings, just use the first one.
            # if True in matches:
            #     first_match_index = matches.index(True)
            #     name = known_face_names[first_match_index]

            # Or instead, use the known face with the smallest distance to the new face
            face_distances = face_recognition.face_distance(
                known_face_encodings, face_encoding)
            best_match_index = np.argmin(face_distances)
            if matches[best_match_index]:
                name = known_face_names[best_match_index]
                can_open = True

            face_names.append(name)

    process_this_frame = not process_this_frame

    # Display the results
    for (top, right, bottom, left), name in zip(face_locations, face_names):
        # Scale back up face locations since the frame we detected in was scaled to 1/4 size
        top *= 4
        right *= 4
        bottom *= 4
        left *= 4

        # Draw a box around the face
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)

        # Draw a label with a name below the face
        cv2.rectangle(frame, (left, bottom - 35),
                      (right, bottom), (0, 0, 255), cv2.FILLED)
        font = cv2.FONT_HERSHEY_DUPLEX
        cv2.putText(frame, name, (left + 6, bottom - 6),
                    font, 1.0, (255, 255, 255), 1)

    return frame, can_open


is_open = False
open_door_thread = Thread(target=open_the_door)
wait_for_close_thread = Thread(target=wait_for_close())
while True:
    if is_open:
        wait_for_close_thread.join()
        open_door_thread.join()
        is_open = False
    else:
        # Grab a single frame of video
        ret, frame = video_capture.read()
        newframe, can_open = detect_face(frame)
        # Display the resulting image
        cv2.imshow('Video', frame)

        if can_open:
            is_open = True
            # start thread
            open_door_thread.start()
            wait_for_close_thread.start()

    # Hit 'q' on the keyboard to quit!
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Release handle to the webcam
video_capture.release()
cv2.destroyAllWindows()
'''
